﻿using System;
using System.Collections.Concurrent;

namespace MethodCaching
{
    /// <summary>
    /// Represents a set of cache decoratated methods 
    /// </summary>
    public class MethodCache
    {
        private readonly ConcurrentDictionary<Delegate,Delegate> _delegates
            = new ConcurrentDictionary<Delegate, Delegate>();

        /// <summary>
        /// Invokes the target <paramref name="function"/>
        /// </summary>        
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method.</param>        
        /// <returns>The result of invoking the target method.</returns>
        public TResult Invoke<TResult>(Func<TResult> function)
        {
            return ((Func<TResult>)_delegates.GetOrAdd(function, d => CacheProvider.Decorate(function)))();
        }

        /// <summary>
        /// Invokes the target <paramref name="function"/>
        /// </summary>
        /// <typeparam name="T">The type of the parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method.</param>
        /// <param name="arg">The argument passed to the parameter of the target method.</param>
        /// <returns>The result of invoking the target method.</returns>
        public TResult Invoke<T,TResult>(Func<T,TResult> function, T arg)
        {
            return ((Func<T, TResult>) _delegates.GetOrAdd(function, d => CacheProvider.Decorate(function)))(arg);            
        }

        /// <summary>
        /// Invokes the target <paramref name="function"/>
        /// </summary>
        /// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method.</param>
        /// <param name="arg1">The argument passed to the first parameter of the target method.</param>
        /// <param name="arg2">The argument passed to the second parameter of the target method.</param>
        /// <returns>The result of invoking the target method.</returns>
        public TResult Invoke<T1,T2, TResult>(Func<T1,T2, TResult> function, T1 arg1, T2 arg2)
        {
            return ((Func<T1,T2,TResult>)_delegates.GetOrAdd(function, d => CacheProvider.Decorate(function)))(arg1,arg2);
        }

        /// <summary>
        /// Invokes the target <paramref name="function"/>
        /// </summary>
        /// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T3">The type of the third parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method.</param>
        /// <param name="arg1">The argument passed to the first parameter of the target method.</param>
        /// <param name="arg2">The argument passed to the second parameter of the target method.</param>
        /// <param name="arg3">The argument passed to the third parameter of the target method.</param>
        /// <returns>The result of invoking the target method.</returns>
        public TResult Invoke<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> function, T1 arg1, T2 arg2, T3 arg3)
        {
            return ((Func<T1, T2, T3, TResult>)_delegates.GetOrAdd(function, d => CacheProvider.Decorate(function)))(arg1, arg2, arg3);
        }

        /// <summary>
        /// Invokes the target <paramref name="function"/>
        /// </summary>
        /// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T3">The type of the third parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="T4">The type of the third parameter of the method that this delegate encapsulates.</typeparam>
        /// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
        /// <param name="function">The target method.</param>
        /// <param name="arg1">The argument passed to the first parameter of the target method.</param>
        /// <param name="arg2">The argument passed to the second parameter of the target method.</param>
        /// <param name="arg3">The argument passed to the third parameter of the target method.</param>
        /// <param name="arg4">The argument passed to the forth parameter of the target method.</param>
        /// <returns>The result of invoking the target method.</returns>
        public TResult Invoke<T1, T2, T3, T4, TResult>(Func<T1, T2, T3, T4, TResult> function, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
        {
            return ((Func<T1, T2, T3, T4, TResult>)_delegates.GetOrAdd(function, d =>  CacheProvider.Decorate(function)))(arg1, arg2, arg3, arg4);
        }
    }
}
